﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SmartMathLibrary.Optimization
{
    /// <summary>
    /// This class provides the searching of an extremum by using the optimized newton algorithm.
    /// </summary>
    [Serializable]
    public class NewtonOptimizer : AbstractSimpleOptimizer
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="NewtonOptimizer"/> class.
        /// </summary>
        /// <param name="sourcePolynomial">The polynomial from which an extremum should be found.</param>
        public NewtonOptimizer(Polynomial sourcePolynomial)
            : base(sourcePolynomial)
        {
        }

        /// <summary>
        /// Finds an extremum of the specified polynomial.
        /// </summary>
        /// <param name="approximationValue">The approximation value of the extremum.</param>
        /// <returns>
        /// The value x of the specified function f(x), where the algorithm finds an extremum.
        /// </returns>
        public double FindExtremum(double approximationValue)
        {
            return this.FindExtremum(approximationValue, 1000, 1e-15);
        }

        /// <summary>
        /// Finds an extremum of the specified polynomial.
        /// </summary>
        /// <param name="approximationValue">The approximation value of the extremum.</param>
        /// <param name="iterations">The maximum numbers of iterations to find the extremum.</param>
        /// <returns>
        /// The value x of the specified function f(x), where the algorithm finds an extremum.
        /// </returns>
        public double FindExtremum(double approximationValue, int iterations)
        {
            return this.FindExtremum(approximationValue, iterations, 1e-15);
        }

        /// <summary>
        /// Finds an extremum of the specified polynomial.
        /// </summary>
        /// <param name="approximationValue">The approximation value of the extremum.</param>
        /// <param name="iterations">The maximum numbers of iterations to find an extremum.</param>
        /// <param name="precision">The precision of the result.</param>
        /// <returns>
        /// The value x of the specified function f(x), where the algorithm finds an extremum.
        /// </returns>
        public double FindExtremum(double approximationValue, int iterations, double precision)
        {
            if (this.SourcePolynomial.Degree < 2)
            {
                throw new ArgumentException("The specified polynomial has to have a minimum degree of two.");
            }

            double tempuri = 0;
            Polynomial firstDerivation = this.SourcePolynomial.Derivative();
            Polynomial secondDerivation = firstDerivation.Derivative();

            for (int i = 0; i < iterations; i++)
            {
                tempuri = approximationValue -
                          (firstDerivation.SolveAt(approximationValue) / secondDerivation.SolveAt(approximationValue));

                if (Math.Abs(tempuri - approximationValue) < precision)
                {
                    this.precisionError = false;
                    this.neededIterations = i;
                    this.relativeError = Math.Abs(tempuri - approximationValue);

                    return tempuri;
                }

                approximationValue = tempuri;
            }

            this.precisionError = true;
            this.neededIterations = iterations;
            this.relativeError = Math.Abs(tempuri - approximationValue);

            return approximationValue;
        }
    }
}